Section top bar
canonComposable bar with title, chips, search, dividers and spacer.
Four screens hand-roll the identity instead of SectionTopBarTitle — decision A6. Phone two-step (AD 27 Sep 2026, acrm !3997, fix/nastaveni-mobil-dvoukrok): Nastavení (SettingsLayout.tsx) below the 720px breakpoint drops the side rail and becomes ⌘K's own two-step — `/nastaveni` with no section shows only the grouped menu full-width (`.nastaveni-page.m-menu`), `/nastaveni/<sekce>` shows only that section's `SectionTopBar` (`flat`) and body (`.m-section`) with a quiet `IconButton icon="arrow-left"` (`.set-back`) as the bar's first item, wired to `navigate('/nastaveni')`. Desktop keeps rail + content side by side unchanged. Side rail at ≥720px would take ~260px on a 390px phone and clip every section's fields — the same two-step shape as the ⌘K palette's list→detail step, not a new pattern. acrm !4029 (27 Sep 2026): new app-only layer apps/web/src/ds/section-top-bar.css (home for bar rules; ds.css is over the 100 kB ratchet and must not grow) — a long name in the bar stays on one line with an ellipsis and SectionTopBarTitle carries `title` with the full name; switch labels in the bar do not wrap. acrm !4044 (27 Sep 2026): the record view switcher on person/company (Activities / Profile) is the canonical `.tchip` with chevron (RULES §8: a switcher in the bar = .tchip + menu), the bespoke `.rvsw-*` was deleted. Menu items without description text (acrm !4051). Phone layout, Attio pattern (owner 2026-09-28, acrm !4088): actions as data — primaryAction {label, icon, onClick, kind} → Fab below 720 px (round only for kind 'create'), secondaryActions (menuOnly, danger, phoneOnly) → one ⋮ bottom sheet, closeAction last; row 1 = title + ⋮, row 2 = tools (view picker left, Filtr/Řadit right). Desktop unchanged. PageHeader forwards the same props. 14 Oct 2026 (owner "schvaleno" 24b v4, proto/detail-jednotne@9600c16a2, acrm !5055; RULES §26): record detail header = ↑↓ · name · ⋮ · ✕ with NO blue primary. Rare actions stay in context (Nový obchod in Nejbližší aktivita + Obchody tab; project Aktualizace in its section); an order shows one button only for the step it waits for. Dev check 14 Oct 1440 + 390: person, company, project, order — 0 primaries in the header, the project label chip gone. Registry repaired 17 Oct 2026 (review F5): PageHeader, RecordCloseButton live in apps/web (see files), not exported by @acrm/ui; dropped from react[]. 20 Oct 2026 (ACRM-569, proto/komunikace-menu-zobrazit-vse@0b7180f3a): `SectionTopBarTitle dot` — identity by colour with no glyph (`.pipe-dot`, a 12 px circle inside the `--tile-sm` frame, CSS in apps/web/src/ds/section-top-bar-dot.css); used by a mail label list. Story: "With dot".
Used in 64 files · 56 areas · 118× — components/ListSearch, components/accounting, components/agent-desk, components/datamart, components/segment, pages/AccountingInvoiceDetailPage …
@acrm/ui <SectionTopBar> <SectionTopBarChip> <SectionTopBarSearch> <SectionTopBarDivider> <SectionTopBarSpacer>.tbar .tchip .acrm-input .vspace +7SourceStoryLegacy card Section top bar.htmlDefault
export const Default = () => {
const [view, setView] = useState('table');
const [q, setQ] = useState('');
return (
<div
style={{
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
overflow: 'hidden',
background: 'var(--bg-canvas)',
}}
>
<SectionTopBar
title={<SectionTopBarTitle name="Firmy" tileIcon="building" tone="blue" />}
primaryAction={
<Button variant="primary">
<Icon name="plus" />
Nová firma
</Button>
}
>
<NavSelect
value={view}
onChange={setView}
options={VIEWS.map((v) => ({ key: v.key, label: v.label, icon: v.icon }))}
/>
<SectionTopBarDivider />
<SectionTopBarSearch placeholder="Hledat firmu…" value={q} onChange={setQ} />
<SectionTopBarChip icon="filter" label="Filtr" value="2" chevron />
<SectionTopBarSpacer />
</SectionTopBar>
<div style={{ padding: 'var(--sp-5)', color: 'var(--fg-muted)', font: 'var(--small)' }}>
{q ? `Hledám „${q}“…` : 'Výpis firem'}
</div>
</div>
);
};Variants
export const Variants = () => {
const [name, setName] = useState('Moje obchody');
return (
<div style={{ display: 'grid', gap: 'var(--sp-5)' }}>
<Frame text="identity = plain icon — a static section (Reporty, Úkoly)">
<SectionTopBar title={<SectionTopBarTitle name="Reporty" icon="chart-line" />} />
</Frame>
<Frame text="identity = colour tile — lists and entities; the tone is the section's colour, not a status">
<SectionTopBar title={<SectionTopBarTitle name="Firmy" tileIcon="building" tone="blue" />}>
<SectionTopBarSpacer />
<SectionTopBarChip icon="filter" label="Filtr" chevron />
</SectionTopBar>
</Frame>
<Frame text="identity = avatar — a person's record">
<SectionTopBar title={<SectionTopBarTitle name="Josef Novák" avatar />}>
<SectionTopBarSpacer />
<Button>
<Icon name="pencil" />
Upravit
</Button>
</SectionTopBar>
</Frame>
<Frame text="identity = heading dropdown — switching between pipelines; the menu is a PickerMenu hung on the title">
<SectionTopBar
title={
<PickerMenu
trigger={(o) => (
<SectionTopBarTitle
name="Obecná pipeline"
tileIcon="layout-columns"
tone="purple"
open={o}
onClick={() => {}}
/>
)}
items={[
{ key: 'obecna', name: 'Obecná pipeline', icon: 'layout-columns' },
{ key: 'tym', name: 'Obchodní tým', icon: 'briefcase' },
]}
value="obecna"
onPick={() => {}}
/>
}
/>
</Frame>
<Frame text="identity = inline rename — click the name and type; Enter saves, Escape cancels (record detail)">
<SectionTopBar
title={
<SectionTopBarTitle
name={name}
tileIcon="star"
tone="amber"
onRename={setName}
renameHint="Přejmenovat"
/>
}
/>
</Frame>
<Frame text="flat — the bar without its bottom hairline, for a host that draws its own separator">
<SectionTopBar flat title={<SectionTopBarTitle name="Nastavení" icon="settings" />} />
</Frame>
{/* Akce jako DATA (`primaryAction` + `secondaryActions`, schváleno 28. 9. 2026, vzor
Attio). Katalog fotí 1280 px, takže tu je desktop; pod 720 px je z primáru Fab
(fab.stories.tsx) a vedlejší akce jsou v jedné nabídce ⋮. */}
<Frame text="actions as data — a create primary + secondary actions; on a phone the primary becomes a round Fab and the secondary actions move into ⋮">
<SectionTopBar
title={<SectionTopBarTitle name="Všechny firmy" tileIcon="building" tone="blue" />}
secondaryActions={[
{ label: 'Vytvořit segment', icon: 'filter-funnel', onClick: () => {} },
]}
primaryAction={{ label: 'Nová firma', icon: 'plus', kind: 'create', onClick: () => {} }}
>
<SectionTopBarDivider />
<SectionTopBarChip icon="filter" label="Filtr" chevron />
</SectionTopBar>
</Frame>
<Frame text="record detail — menuOnly actions live in ⋮ on every width, danger is red, closeAction stays last; a non-create primary becomes an extended Fab on a phone">
<SectionTopBar
title={
<SectionTopBarTitle name="Receipt-2121-1804.pdf" tileIcon="file-text" tone="amber" />
}
secondaryActions={[
{ label: 'Zaevidovat jako majetek', icon: 'package', onClick: () => {} },
{ label: 'Nastavení práv', icon: 'users', menuOnly: true, onClick: () => {} },
{
label: 'Smazat dokument',
icon: 'trash',
menuOnly: true,
danger: true,
onClick: () => {},
},
]}
primaryAction={{ label: 'Zaúčtovat', icon: 'receipt', onClick: () => {} }}
closeAction={<IconButton icon="x" title="Zavřít" onClick={() => {}} />}
/>
</Frame>
</div>
);
};States
export const States = () => {
const [openChip, setOpenChip] = useState(true);
return (
<div style={{ display: 'grid', gap: 'var(--sp-5)' }}>
<Frame text="chip — quiet by default, tinted while its menu is open (.tchip.open)">
<SectionTopBar
title={<SectionTopBarTitle name="Sales" tileIcon="chart-line" tone="rose" />}
>
<SectionTopBarChip icon="arrows-sort" label="Řadit" value="Vytvořeno" chevron />
<SectionTopBarChip
icon="filter"
label="Filtr"
value="2"
chevron
open={openChip}
onClick={() => setOpenChip((v) => !v)}
/>
<SectionTopBarSpacer />
</SectionTopBar>
</Frame>
<Frame text="search — the box is quiet until it is focused; the accent border comes from :focus-within on the wrapper, the input itself has no ring">
<SectionTopBar title={<SectionTopBarTitle name="Lidé" tileIcon="user" tone="purple" />}>
<SectionTopBarSearch placeholder="Hledat osobu…" />
<SectionTopBarSpacer />
</SectionTopBar>
</Frame>
<Frame text="saving a rename — the field is not editable again until the save comes back (renaming)">
<SectionTopBar
title={
<SectionTopBarTitle
name="Bonitní Pražané"
tileIcon="filter"
tone="green"
onRename={() => {}}
renaming
/>
}
/>
</Frame>
{/* Telefon (≤720 px) tu úmyslně NENÍ: řádek nástrojů se posouvá do strany a primár je Fab
(!4088, !4114) — řídí to media query podle VIEWPORTU, takže v desktopovém katalogu by šlo
jen o uříznutou desktopovou lištu (vizuální audit AD 28. 9. 2026). Popis: Usage → Phone. */}
</div>
);
};With icon & kbd
export const WithIconAndKbd = () => (
<div style={{ display: 'grid', gap: 'var(--sp-5)' }}>
<Frame text="every control in the bar is quiet and icon-led: chips carry the field icon, actions are icon + verb, and only the primary action is filled">
<SectionTopBar
title={<SectionTopBarTitle name="Sales" tileIcon="chart-line" tone="rose" />}
primaryAction={
<Button variant="primary" kbd="⌘N">
<Icon name="plus" />
Nový obchod
</Button>
}
>
<SectionTopBarChip icon="arrows-sort" label="Řadit" value="Vytvořeno" chevron />
<SectionTopBarDivider />
<SectionTopBarChip icon="filter" label="Filtr" value="2" chevron />
<SectionTopBarSpacer />
<Button>
<Icon name="download" />
Export
</Button>
</SectionTopBar>
</Frame>
<span style={label}>
A shortcut chip belongs on the action that answers to it (here „Nový obchod“ = ⌘N); the bar
itself carries no shortcuts.
</span>
</div>
);Composition
export const Composition = () => {
const [q, setQ] = useState('');
return (
<div
style={{
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
overflow: 'hidden',
background: 'var(--bg-canvas)',
}}
>
<SectionTopBar
title={<SectionTopBarTitle name="Sales" tileIcon="chart-line" tone="rose" />}
primaryAction={
<Button variant="primary">
<Icon name="plus" />
Nový obchod
</Button>
}
>
<SectionTopBarSearch placeholder="Hledat obchod…" value={q} onChange={setQ} />
<SectionTopBarChip icon="filter" label="Filtr" value="2" chevron />
<SectionTopBarSpacer />
</SectionTopBar>
{/* the filter bar is the second row under the top bar — the shipped order */}
<div className="fb">
<button type="button" className="fb-chip">
<Icon name="target" />
<span className="k">Fáze</span>
<span className="v">Jednání</span>
<Icon name="chevron-down" className="chev" />
</button>
<Button variant="ghost">
<Icon name="plus" />
Filtr
</Button>
</div>
<div style={{ padding: 'var(--sp-5)', color: 'var(--fg-muted)', font: 'var(--small)' }}>
Výpis obchodů{q ? ` — hledám „${q}“` : ''}
</div>
</div>
);
};